Gradle's build behavior will vary with regards to the user's configuration. Gradle is engineered to provide each user the ability to configure it such that it exhibits the desired externally visible behavior.
The functional view is focused on the functional capabilities of Gradle. Notably, its ability to build a software project.
We believe that by narrowing the scope of our functional view to that of a Java project build specifically, we are able to better identify the architecturally significant components. The component diagram addresses important scenario 1 by showing how functional components interact together to perform a build.
Therefore, we have decided to use a Java project build as the scope of our functional view. Albeit not necessary for a succesful build, our scope includes the Gradle daemon since it improves performance significantly.
Readers will notice that the functionality described in this view mostly pertains to Gradle's initialization and configuration. Since Gradle follows a microkernel architectural style, we focus our view not on the build behavior, but rather the systems which allow users to configure the build behavior. Exactly what is being built and executed is dictated by user-specific plugins and tasks, which we categorize as extended functionality.
Content | Description |
---|---|
Gradle Build Lifecycle | Introduces the inialization, configuration and execution phase of Gradle's build lifecycle. |
Initialization | Showcases the functional components pertaining to the initialization phase. |
Configuration | Describes the components relating to the configuration phase. |
Execution | Gives a brief explanation of the execution system and explains why our AD does not go in much depth when describing the execution system. |
Gradle Daemon | Details the Gradle Daemon, a lingering process used to improve build time. |
Plugin Application Sequence Diagram | Details the sequence of events necessary for a plugin to be applied to a project. |
To properly understand the way in which Gradle processes a build, it is important to understand the Gradle Build Lifecycle.
Phase | Description |
---|---|
1. Initialization phase | A build begins in this phase. During this phase, the command line arguments are parsed, the settings script file is found and loaded into a Settings configuration object. The Settings configuration object is crucial since it contains the description of projects to build. |
2. Configuratation phase | During this phase, the configuration script files are loaded into run-time configuration objects, all the configuration objects (except Settings) are initialized in this phase (e.g. a Project configuration objects is initialized by loading its corresponding script file into it). Since plugins configure projects at run-time, plugins are applied onto projects during this phase. |
3. Execution phase | During this final phase, a Task execution tree is built with regards to the task dependencies. The tasks are then executed in a correct order using the tree. Once this phase is over, the build is complete. |
More information can be found in Gradle's documentation.
Functional Component | Description |
---|---|
Launcher | The Launcher is Gradle's entry point. (relating to the Entry Point class in the code). It is responsible for any initial bootstraping process, it then forwards any input arguments to the ActionManager which would use those arguments to initialize and execute a build. |
ActionManager | The ActionManager's responsibility is to build an action. It receives arguments and parses them (in the form of a CommandLineExecution). It then initializes the build action and forwards the result to the Initializer. The Initializer may run on the current process or on a seperate process depending on the input arguments. To run the Initializer on a seperate process, the ActionManager will instead forward the arguments to a DaemonClient. |
DeamonClient | The DaemonClient's main responsibility is to find a lingering Daemon and forward the build action to it. It creates a Daemon if it cannot find one. More information on why it does this can be found in the Gradle Daemon section. |
Daemon | The Daemon's main responsibility is to receive build actions from the DaemonClient and to execute the action via a (process local) Initializer. |
Initializer | The Initializer's main responsibility is to coordinate the inialization phase of the Gradle Build Lifecycle. It first starts by loading the settings script file into a Settings configuration object. The initializer then launches the configuration phase by interfacing with configuration subsytem. The configuration phase requires a loaded Settings configuration object which contains valuable information such as which projects to build (therefore which Project configuration object to configure). Once, the configuration phase is done, the Initializer launches the execution phase by interfacing with the Execution subsystem. |
Settings | Settings is a configuration object. Its main responsibility is to locate and load the settings script file (into itself). This step includes identifying all the projects required for the build (dictated in the settings' script file). |
Interface | Description |
---|---|
createAndRunBuildAction | This interface provides a way to parse the Launcher's input arguments and intialize a BuildAction. A BuildAction simply simply represents a Gradle build to be performed. The BuildAction is created by an action factory, for example DefaultCommandLineActionFactory. |
executeOnDaemon | This interfaces provides a way for the ActionManager to forward a BuildAction to the DaemonClient. The Daemon Client then finds a lingered Daemon process on which the BuildAction can be executed. |
executeBuildAction | This interface is provided by the Initializer and simply begins the execution of a BuildAction. The Initializer includes classes such as the BuildActionExecuter. |
Message | The build action is sent between a DaemonClient and the Daemon through a Message. The message is sent from the Client through a DaemonClientConnection. The message is received in a DaemonConnection |
findAndLoadSettings | Creates an instance of a Settings configuration object. This is done via a SettingsLoader which takes care of the loading of the settings' script file into the configuration object |
startExecution | Forwards the necessary information to the Execution System (projects, tasks, ...) and begins the final phase of the Gradle build lifecylce, the execution phase. |
createProjects | Loads the Project's build.gradle file in to the project configuration object. Further initializes the project configuration object instance by resolving any missing artifact such as plugins. Applies the necessary plugins to the project. |
Functional Component | Description |
---|---|
Task | The Task component's main responsibility is to represent a "unit of work". A task contains an ordered list of runnable actions (much like a linked-list) and keeps track of the tasks it depends on (must be executed before). |
Project | A Project configuration object is mapped to a build.gradle file. It may contain subprojects but there is only one root project. A Project contains tasks and may have plugins applied on it. It is the glue that connects most components together and it represents source code that must be built. |
Build Cache | In terms of functionalities the BuildCache handles the storing and retrieval of cached files (task outputs), it is explored in depth in the information view. |
Plugin | A Plugin can be used to extend a project in many ways. The most common way is to add a (or multiple) tasks to a project. Plugins are specified in a project's script file. |
Artifact Repository | A repository contains artifacts that follow a specific format, for example "[artifact]-[version].[ext]". when a project's artifact repositories are missing an important artifact (such as a plugin), then the artifact must be fetched from a Source. |
Source | An abstract components which is used to model any entity that exposes a way to retrieve artifacts (such as files). The artifact repository will resolve missing artifacts from the source via the strategy design pattern. |
Interface | Description |
---|---|
execute | Executes the task. The actual work done is done by Actions. To execute a Task, the list of actions are retreived, and then executed in order. This work is wrapped in a TaskExecution. This is called by the execution system. |
registerTask | A project contains tasks, therefore the registerTask interface is used to register a task to a project. |
configureProject | Represents the inversion of control, meaning that a Project gives an "instance" of itself to Plugin such that Plugin can configure the instance of Project (by addding tasks for example). |
getCachedOutput | Accesses a previously computed task output from the BuildCache. |
applyPlugin | The Plugin component applies itself to a Project instance in order to given the plugin increased functionality through making the Project aware of available Tasks. The PluginManager keeps track of which plugins have been applied. |
resolveArtifact | The artifact resolution uses the strategy design pattern to define a resolution protocol specific to a perticular Source. The resolution strategy should be such that artifacts are only fetched from a remote Source if they are not present locally. This is evidenced by the FileResolver. |
getArtifact | This is a conceptual interface which abstracts the way a source exposes artifacts to the ArtifactRepository. Since each Source may expose artifacts differently (e.g. via an HTTP web server), we will not go into details. |
The execution system is architecturally simple and its complexity is present mostly at the software design level. Our AD, strives to be both clear and concise, therefore we will present a high-level overview of the execution system.
Once the configuration phase is over the initialization system begins the execution phase. The execution system builds a task dependency tree and executes tasks in a correct order (with regards to the dependency tree). Since tasks can be seen as a linked-list of actions, the execution system further ensures that the actions of each task are executed in the correct order.
Gradle strives to be very performant. It achieves this partly by utilizing a daemon process.
Gradle's initialization is loosely coupled with the configuration and execution of the build, therefore running the build on a seperate process is as simple as delivering a BuildAction message to another process, the Gradle daemon.
The Gradle daemon is a lingering background process which maintains the result of any previously executed build's bootstraping in memory.
The Gradle daemon can speed up builds by 15-75%. The daemon allows redundant, yet expensive, bootstrapping operations to be skipped (e.g. loading of large libraries into memory) by keeping the preivious build's bootstrapping results in-memory.
This performance does come with a cost, alot of memory can be used by the Gradle daemon. However, due to architectural princple #2, which dictates that build performance takes precedence over memory utilization, this is not a major issue.
It is strongly discouraged to execute Gradle builds without the daemon since in most scenarios, the daemon does not consume much memory.
One of our important scenarios is the application of plugins to increase Gradle's functionality.
Plugin application is not straight forward and unintuitive (evidenced by the inversion of control). Therefore, we believe that it is important to properly showcase the execution sequence which leads to a plugin being applied on a specific Project.
We hope that this diagram helps readers understand that the plugin configures the project at run-time, after the initialization of Gradle.
In the diagram, we notice that Project appears twice. In fact, readers will notice that the same instance "proj:Project" calls apply on Plugin and Plugin calls configure back on "proj:Project".